/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright(c) 2021 Huawei Technologies Co., Ltd */

#ifndef HIUDK3_WQ_H
#define HIUDK3_WQ_H

#include <linux/types.h>
#include "securec.h"
#include "hiudk3_common.h"

struct hinic3_wq {
    u16 cons_idx;
    u16 prod_idx;

    u32 q_depth;
    u16 idx_mask;
    u16 wqebb_size_shift;
    u16 num_wq_pages;
    u32 wqebbs_per_page;
    u16 wqebbs_per_page_shift;
    u16 wqebbs_per_page_mask;

    struct hinic3_dma_addr_align *wq_pages;

    dma_addr_t wq_block_paddr;
    u64 *wq_block_vaddr;

    void *dev_hdl;
    u32 wq_page_size;
    u16 wqebb_size;
} ____cacheline_aligned;

int hiudk_wq_create(void *udkdev, struct hinic3_wq *wq, u32 q_depth, u16 wqebb_size);
void hiudk_wq_destroy(void *udkdev, struct hinic3_wq *wq);

#define WQ_MASK_IDX(wq, idx) ((idx) & (wq)->idx_mask)
#define WQ_MASK_PAGE(wq, pg_idx) (((pg_idx) < (wq)->num_wq_pages) ? (pg_idx) : 0)
#define WQ_PAGE_IDX(wq, idx) ((idx) >> (wq)->wqebbs_per_page_shift)
#define WQ_OFFSET_IN_PAGE(wq, idx) ((idx) & (wq)->wqebbs_per_page_mask)
#define WQ_GET_WQEBB_ADDR(wq, pg_idx, idx_in_pg) \
    ((u8 *)(wq)->wq_pages[(pg_idx)].align_vaddr + ((idx_in_pg) << (wq)->wqebb_size_shift))
#define WQ_IS_0_LEVEL_CLA(wq) ((wq)->num_wq_pages == 1)

static inline u16 hinic3_wq_free_wqebbs(struct hinic3_wq *wq)
{
    return (u16)(wq->q_depth - ((wq->q_depth + wq->prod_idx - wq->cons_idx) & wq->idx_mask) - 1);
}

static inline bool hinic3_wq_is_empty(const struct hinic3_wq *wq)
{
    return WQ_MASK_IDX(wq, wq->prod_idx) == WQ_MASK_IDX(wq, wq->cons_idx);
}

static inline void *hinic3_wq_get_one_wqebb(struct hinic3_wq *wq, u16 *pi)
{
    *pi = WQ_MASK_IDX(wq, wq->prod_idx);
    wq->prod_idx++;

    return WQ_GET_WQEBB_ADDR(wq, WQ_PAGE_IDX(wq, *pi), WQ_OFFSET_IN_PAGE(wq, *pi));
}

static inline void *hinic3_wq_get_multi_wqebbs(
    struct hinic3_wq *wq, u16 num_wqebbs, u16 *prod_idx, void **second_part_wqebbs_addr, u16 *first_part_wqebbs_num)
{
    u32 pg_idx, off_in_page;

    *prod_idx = WQ_MASK_IDX(wq, wq->prod_idx);
    wq->prod_idx += num_wqebbs;

    pg_idx = WQ_PAGE_IDX(wq, *prod_idx);
    off_in_page = WQ_OFFSET_IN_PAGE(wq, *prod_idx);

    if ((off_in_page + num_wqebbs) > wq->wqebbs_per_page) {
        /* wqe across wq page boundary */
        *second_part_wqebbs_addr = WQ_GET_WQEBB_ADDR(wq, (WQ_MASK_PAGE(wq, pg_idx + 1)), 0u);
        *first_part_wqebbs_num = (u16)(wq->wqebbs_per_page - off_in_page);
    } else {
        *second_part_wqebbs_addr = NULL;
        *first_part_wqebbs_num = num_wqebbs;
    }

    return WQ_GET_WQEBB_ADDR(wq, pg_idx, off_in_page);
}

static inline void hinic3_wq_put_wqebbs(struct hinic3_wq *wq, u16 num_wqebbs)
{
    wq->cons_idx += num_wqebbs;
}

static inline void *hinic3_wq_wqebb_addr(struct hinic3_wq *wq, u16 idx)
{
    return WQ_GET_WQEBB_ADDR(wq, WQ_PAGE_IDX(wq, idx), WQ_OFFSET_IN_PAGE(wq, idx));
}

static inline void *hinic3_wq_read_one_wqebb(struct hinic3_wq *wq, u16 *cons_idx)
{
    *cons_idx = WQ_MASK_IDX(wq, wq->cons_idx);

    return hinic3_wq_wqebb_addr(wq, *cons_idx);
}

static inline u64 hinic3_wq_get_first_wqe_page_addr(struct hinic3_wq *wq)
{
    return wq->wq_pages[0].align_paddr;
}

static inline void hinic3_wq_reset(struct hinic3_wq *wq)
{
    u16 pg_idx;

    wq->cons_idx = 0;
    wq->prod_idx = 0;

    for (pg_idx = 0; pg_idx < wq->num_wq_pages; pg_idx++)
        (void)memset_s(wq->wq_pages[pg_idx].align_vaddr, wq->wq_page_size, 0, wq->wq_page_size);
}

struct hiudk_wq_ops {
    int (*hiudk_wq_create)(void *hwdev, struct hinic3_wq *wq, u32 q_depth, u16 wqebb_size);
    void (*hiudk_wq_destroy)(struct hinic3_wq *wq);
};

#endif
